Skip to content

fix(rsc): handle export * re-exports in use client and use server modules#1234

Merged
hi-ogawa merged 4 commits into
vitejs:mainfrom
james-elicx:claude/busy-raman-3dee83
May 29, 2026
Merged

fix(rsc): handle export * re-exports in use client and use server modules#1234
hi-ogawa merged 4 commits into
vitejs:mainfrom
james-elicx:claude/busy-raman-3dee83

Conversation

@james-elicx

@james-elicx james-elicx commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

The use-client proxy transform in @vitejs/plugin-rsc throws unsupported ExportAllDeclaration whenever a "use client" file contains export * from '...'. Reported downstream in cloudflare/vinext#1352, where Next.js's app-dir test suite hits this with a real-world components/export-all/index.js fixture.

fixes #1233

Reproduction

A new fixture under packages/plugin-rsc/examples/basic/src/routes/export-all/ reproduces the original error on main:

[plugin rsc:use-client] .../routes/export-all/index.tsx
Error: unsupported ExportAllDeclaration
    at transformProxyExport (.../transforms/index.js:338:90)

It builds cleanly with the fix.

Fix

  1. Pure transforms (proxy-export.ts, wrap-export.ts) now handle the export * as ns from '...' form directly, since the namespace name is statically known.

  2. Plugin layer (plugin.ts) introduces an expandExportAllDeclarations helper used by both vitePluginUseClient and vitePluginUseServer. For each bare export * from '...' declaration, it resolves the source, recursively collects the named exports via AST walk, and rewrites the source to export { a, b, c } from '...' before the proxy transform runs. This mirrors what Next.js's webpack plugin does to pre-resolve export * into named exports.

    • In build mode it uses this.load({ id }).code (which Rollup populates).
    • In dev mode ModuleInfo.code is unsupported and transformRequest returns module-runner output (__vite_ssr_exportName__(...)) that lacks readable ESM exports, so it falls back to fs.readFile + transformWithOxc to get standard JS the AST walk can read.

Test plan

  • New unit coverage in proxy-export.test.ts for export * as ns from, the post-resolution form, the ignoreExportAllDeclaration escape hatch, and the still-thrown unresolved-bare case; updated wrap-export.test.ts snapshot. All 428 unit tests pass.
  • New e2e test export * in basic.test.ts against the new fixture. All 151 basic e2e tests pass in both dev and build modes.
  • Verified original build error reproduces on main (stashed the fix, rebuilt, build fails with the exact error from the issue) and disappears with the fix.

The `use-client` proxy transform threw `unsupported ExportAllDeclaration`
on any `"use client"` file containing `export * from '...'`. Pre-resolve
bare `export *` declarations to explicit named re-exports before running
the proxy transform (mirroring Next.js's webpack plugin behavior), and
handle `export * as ns from` directly in the pure proxy/wrap transforms
since the name is statically known.

Closes cloudflare/vinext#1352
@james-elicx james-elicx marked this pull request as ready for review May 21, 2026 21:41
@hi-ogawa hi-ogawa self-assigned this May 22, 2026
Comment thread packages/plugin-rsc/src/plugin.ts Outdated

@hi-ogawa hi-ogawa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this. The idea makes sense to me.

@james-elicx james-elicx requested a review from hi-ogawa May 22, 2026 09:07

@hi-ogawa hi-ogawa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-up. I reviewed this again, and it looks good to merge.

I left a few comments for things I might clean up separately on my side. Mainly I want to extract expandExportAllDeclarations into a pure-ish utility so we can unit test it directly. There are also a few edge cases I want to tweak for export name conflicts and load/parse failure mode. In case interested, here is my follow-up note: https://gist.github.com/hi-ogawa/cf434aff19cc869e6cfae366851db0c7

return names
}

async function expandExportAllDeclarations(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably it's better to extract this as src/transforms/ util with unit test.

Comment on lines +1 to +3
'use client'

export * from './named'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

coverage for "use server" too

Comment on lines +1423 to +1436
try {
const raw = await fs.promises.readFile(resolvedId, 'utf-8')
moduleCode = await transformSourceForExportScan(raw, resolvedId)
} catch {
return []
}
if (!moduleCode) return []

let ast: Awaited<ReturnType<typeof parseAstAsync>>
try {
ast = await parseAstAsync(moduleCode)
} catch {
return []
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can start from surfacing these errors to start with so it's easier to iterate on unsupported case.

@hi-ogawa hi-ogawa changed the title fix(rsc): handle export * re-exports in use client modules fix(rsc): handle export * re-exports in use client and use server modules May 29, 2026
throw Object.assign(new Error('unsupported ExportAllDeclaration'), {
pos: node.start,
})
if (node.type === 'ExportAllDeclaration') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, this changes looks unneeded as it cannot be useful for server function case at least.

@hi-ogawa hi-ogawa merged commit dd94ec6 into vitejs:main May 29, 2026
20 checks passed
@james-elicx james-elicx deleted the claude/busy-raman-3dee83 branch May 29, 2026 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[plugin-rsc] use-client transform throws on export * from (ExportAllDeclaration)

2 participants